package de.saarland.uni.lego3d.server.gui;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;

import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.universe.SimpleUniverse;

import de.saarland.uni.lego3d.server.KinectAction;
import de.saarland.uni.lego3d.server.Legobrick;
import de.saarland.uni.lego3d.server.gui.components.HistoryPanel;
import de.saarland.uni.lego3d.server.gui.components.Java3DPanel;
import de.saarland.uni.lego3d.server.gui.components.TempModelPanel;
import de.saarland.uni.lego3d.server.gui.handler.KeyHandler;

/**
 * This class describes the main GUI of the Lego3D application.
 * It takes care of the current viewpoint, defines the context areas and handles new bricks and kinect messages.
 * Basically, it defines the overall layout of our application using Java3D canvases in most cases.
 */
public class MainGUI extends JFrame {
    
	private static final long serialVersionUID = 1L;

	private Robot mRobot;
	
	/**
	 * 3 dimensional data structure representing the lego room in x, y and z direction,
	 * initially it is filled with 0, if somewhere stands a 1, then there is a brick
	 */
	public static int[][][] legoRoom = new int[50][30][34];
	
	/**
	 * Keeps the current model
	 */
	public BranchGroup rootBranchGroup;
	
	/**
	 * Universe
	 */
	public MultipleUniverse universe = new MultipleUniverse();
	
	public KinectAction current_kinectMsg;
	public Legobrick current_legoBrick;
	
	private Java3DPanel currentPanel;

	private TempModelPanel tmpPanel;
	private HistoryPanel historyModelView;
	
	public MainGUI () {
		super("Saarland University - Lego3D");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setResizable(true);
		//Fullscreen. But for the demo, we will show kinect visual feedback as well, so we do net have fullscreen enabled
//		setExtendedState(Frame.MAXIMIZED_BOTH);
		setPreferredSize(new Dimension(900,768));
		getContentPane().setBackground(Color.BLACK);
		getContentPane().setLayout(new BorderLayout());
//		setUndecorated(true);
		
		//init legoRoom array
		for (int i = 0; i < 50; i++) {
			for (int j = 0; j < 30; j++) {
				for (int k = 0; k < 34; k++) {
					legoRoom[i][j][k] = 0;
				}
			}
		}
		
		//Set KeyListener
		addKeyListener(new KeyHandler());
		
		//Set MouseListener
		try {
			mRobot = new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
		}
		
		//Add Java3D panel
		initRootBranchGroup();
		universe.addBranchGraph(rootBranchGroup);
		
		
		//Current View
		currentPanel = new Java3DPanel(Java3DPanel.VIEWPOINT_FRONT, this);
		getContentPane().add(currentPanel, BorderLayout.CENTER);
		currentPanel.setBackground(Color.DARK_GRAY);
		currentPanel.setCurrent();
		
		//Deselected Views
		JPanel deselectedViews = new JPanel();
		deselectedViews.setBackground(Color.BLACK);
		deselectedViews.setLayout(new GridLayout(1, 4));
		
		Java3DPanel deselected1 = new Java3DPanel(Java3DPanel.VIEWPOINT_LEFT, this);
		deselected1.setBackground(Color.DARK_GRAY);
		deselected1.setDeselected();
		
		Java3DPanel deselected2 = new Java3DPanel(Java3DPanel.VIEWPOINT_RIGHT, this);
		deselected2.setBackground(Color.DARK_GRAY);
		deselected2.setDeselected();
		
		Java3DPanel deselected3 = new Java3DPanel(Java3DPanel.VIEWPOINT_BACK, this);
		deselected3.setBackground(Color.DARK_GRAY);
		deselected3.setDeselected();
		
		Java3DPanel deselected4 = new Java3DPanel(Java3DPanel.VIEWPOINT_TOP, this);
		deselected4.setBackground(Color.DARK_GRAY);
		deselected4.setDeselected();
		
		deselectedViews.add(deselected1, 0);
		deselectedViews.add(deselected2, 1);
		deselectedViews.add(deselected3, 2);
		deselectedViews.add(deselected4, 3);
		getContentPane().add(deselectedViews, BorderLayout.NORTH);
		
		//Temporary models
		tmpPanel = new TempModelPanel(this);
		getContentPane().add(tmpPanel, BorderLayout.EAST);
		
		//History models
		historyModelView = new HistoryPanel(this);
		getContentPane().add(historyModelView, BorderLayout.WEST);
						
		pack();
		setVisible(true);
	}
	
	public void switchPanels(Java3DPanel deselected) {
		JPanel parent1 = (JPanel) currentPanel.getParent();
		JPanel parent2 = (JPanel) deselected.getParent();
		
		Component[] comps = parent2.getComponents();
		int idx = -1;
		for (int i = 0; i < comps.length; i++) {
			if (comps[i] == deselected) {
				idx = i;
				break;
			}
		}
		
		parent1.remove(currentPanel);
		parent2.remove(deselected);
		
		currentPanel.setDeselected();
		deselected.setCurrent();
		
		parent1.add(deselected, BorderLayout.CENTER);
		parent2.add(currentPanel, idx);
		
		currentPanel = deselected;
		parent1.revalidate();
		parent2.revalidate();
		repaint();
	}
	
	public void setNewKinectMsg(KinectAction current_kinectMsg_) {
		if (current_kinectMsg_ != null) {
			if (current_kinectMsg_.isAction_brick() && current_kinectMsg != null && !current_kinectMsg.isAction_brick()) {
				mRobot.mousePress(InputEvent.BUTTON1_MASK);
				currentPanel.setCursor(null);
			} else if (!current_kinectMsg_.isAction_brick() && current_kinectMsg != null && current_kinectMsg.isAction_brick()) {
				mRobot.mouseRelease(InputEvent.BUTTON1_MASK);
				currentPanel.setCursor(Cursor.getDefaultCursor());
			}
			if (!current_kinectMsg_.isAction_brick()) {
				PointerInfo a = MouseInfo.getPointerInfo();
		        Point b = a.getLocation();
		        int x = (int) b.getX();
		        int y = (int) b.getY();
		        mRobot.mouseMove(x + (current_kinectMsg_.getDirection_right() - current_kinectMsg_.getDirection_left()) / 7, 
		        		y + (current_kinectMsg_.getDirection_down() - current_kinectMsg_.getDirection_up()) / 7);
			}
		}
		current_kinectMsg = current_kinectMsg_;
	}
	
	public void setNewBrick(Legobrick current_legoBrick_) {
		LegoBrick3D newBrick = new LegoBrick3D(current_legoBrick_);
/***********************************************************************/		
		//creates a collision detector for the new brick 
//		CollisionDetector cd = new CollisionDetector(newBrick);
//		cd.setSchedulingBounds(newBrick.getBounds());
//		newBrick.getTransformGroup().addChild(cd);
/**********************************************************************/		
		rootBranchGroup.addChild(newBrick);
	}
	
	public void addComposedBrick(LegoBrick3D newBrick) {
		rootBranchGroup.addChild(newBrick);
	}
	
	// Create a Texture object
	private Texture createTexture() {
		String filename = "/Texture1.jpg";
		// Load the texture
		TextureLoader loader = new TextureLoader(this.getClass().getResource("/de/saarland/uni/lego3d/textures/"+filename), null);
	    ImageComponent2D image = loader.getImage();

	    if (image == null) {
	      System.out.println("load failed for texture: " + filename);
	    }

	    // can't use parameterless constuctor
	    Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
	        image.getWidth(), image.getHeight());
	    texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
	    texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
	    texture.setImage(0, image);

	    return texture;
	}
	
	private void initRootBranchGroup() {
		rootBranchGroup = new BranchGroup();
		rootBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);  //Needed to change/remove transformationgroups of bricks
		rootBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);	  //Needed to read transformationgroups of bricks
		rootBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND); //Needed to re-add transformationgroups of bricks
		rootBranchGroup.setCapability(BranchGroup.ALLOW_PICKABLE_READ);
		rootBranchGroup.setCapability(BranchGroup.ALLOW_PICKABLE_WRITE);
		
		//Draw floor (30 x 30 units)
		Appearance polygon1Appearance = new Appearance();
		
		//add texture
		Texture tex = createTexture();
		tex.setBoundaryModeT(Texture.WRAP);
		tex.setBoundaryModeS(Texture.WRAP);
		polygon1Appearance.setTexture(tex);
		TextureAttributes ta=new TextureAttributes(TextureAttributes.MODULATE, new Transform3D(), new Color4f(), TextureAttributes.NICEST);
		polygon1Appearance.setTextureAttributes(ta);

		// Map the texture coordinatex
		TexCoord2f[] texCoords = new TexCoord2f[4];
		for (int i = 0; i < texCoords.length; i += 4) {
			texCoords[i] = new TexCoord2f(0.0f, 0.0f);
			texCoords[i + 1] = new TexCoord2f(0.0f, 1.0f);
			texCoords[i + 2] = new TexCoord2f(1.0f, 1.0f);
			texCoords[i + 3] = new TexCoord2f(1.0f, 0.0f);
		}
		
		QuadArray polyBottom = new QuadArray (4, QuadArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2);
		polyBottom.setCoordinate (0, new Point3f (-1.5f, 0f, 0f));
		polyBottom.setCoordinate (1, new Point3f (1.5f, 0f, 0f));
		polyBottom.setCoordinate (2, new Point3f (1.5f, 0f, -3.0f));
		polyBottom.setCoordinate (3, new Point3f (-1.5f, 0f, -3.0f));
		
		polyBottom.setTextureCoordinates(0, 0, texCoords);
		
		rootBranchGroup.addChild(new Shape3D(polyBottom,polygon1Appearance));
				
		//Set lights
		//1
		Color3f light1Color = new Color3f(1f, 1f, 1f);
		BoundingSphere bounds = new BoundingSphere(new Point3d(-6.5,4.0,5.0), 50.0);
		Vector3f light1Direction = new Vector3f(1.0f, -1.0f, -1.0f);
		DirectionalLight light = new DirectionalLight(light1Color, light1Direction); //Setzen
		light.setInfluencingBounds(bounds);
		rootBranchGroup.addChild(light);
		
		//1
		light1Color = new Color3f(1f, 1f, 1f);
		bounds = new BoundingSphere(new Point3d(-6.5,4.0,-8.0), 50.0);
		light1Direction = new Vector3f(1.0f, -1.0f, 1.0f);
		light = new DirectionalLight(light1Color, light1Direction); //Setzen
		light.setInfluencingBounds(bounds);
		rootBranchGroup.addChild(light);
		
		//1
		light1Color = new Color3f(1f, 1f, 1f);
		bounds = new BoundingSphere(new Point3d(6.5,4.0,-8.0), 50.0);
		light1Direction = new Vector3f(-1.0f, -1.0f, 1.0f);
		light = new DirectionalLight(light1Color, light1Direction); //Setzen
		light.setInfluencingBounds(bounds);
		rootBranchGroup.addChild(light);
		
		//1
		light1Color = new Color3f(1f, 1f, 1f);
		bounds = new BoundingSphere(new Point3d(6.5,4.0,5.0), 50.0);
		light1Direction = new Vector3f(-1.0f, -1.0f, -1.0f);
		light = new DirectionalLight(light1Color, light1Direction); //Setzen
		light.setInfluencingBounds(bounds);
		rootBranchGroup.addChild(light);

	}
	
	
	public void initRootBranchGroup(BranchGroup bg) {
		initRootBranchGroup();
		universe.cleanup();
		universe = new MultipleUniverse();
		universe.addBranchGraph(bg);
		
		getContentPane().removeAll();
		getContentPane().add(historyModelView, BorderLayout.WEST);
		
		//Current View
		currentPanel = new Java3DPanel(Java3DPanel.VIEWPOINT_FRONT, this);
		getContentPane().add(currentPanel, BorderLayout.CENTER);
		currentPanel.setBackground(Color.DARK_GRAY);
		currentPanel.setCurrent();
		
		//Deselected Views
		JPanel deselectedViews = new JPanel();
		deselectedViews.setBackground(Color.BLACK);
		deselectedViews.setLayout(new GridLayout(1, 4));
		
		Java3DPanel deselected1 = new Java3DPanel(Java3DPanel.VIEWPOINT_LEFT, this);
		deselected1.setBackground(Color.DARK_GRAY);
		deselected1.setDeselected();
		
		Java3DPanel deselected2 = new Java3DPanel(Java3DPanel.VIEWPOINT_RIGHT, this);
		deselected2.setBackground(Color.DARK_GRAY);
		deselected2.setDeselected();
		
		Java3DPanel deselected3 = new Java3DPanel(Java3DPanel.VIEWPOINT_BACK, this);
		deselected3.setBackground(Color.DARK_GRAY);
		deselected3.setDeselected();
		
		Java3DPanel deselected4 = new Java3DPanel(Java3DPanel.VIEWPOINT_TOP, this);
		deselected4.setBackground(Color.DARK_GRAY);
		deselected4.setDeselected();
		
		deselectedViews.add(deselected1, 0);
		deselectedViews.add(deselected2, 1);
		deselectedViews.add(deselected3, 2);
		deselectedViews.add(deselected4, 3);
		getContentPane().add(deselectedViews, BorderLayout.NORTH);
		
		//Temporary models
		tmpPanel = new TempModelPanel(this);
		getContentPane().add(tmpPanel, BorderLayout.EAST);
						
		pack();
	}
	
	public SimpleUniverse getUniverse() {
		return universe.getViewer(currentPanel.viewpoint).getUniverse();
	}
	
	public void addAndSaveModel(BufferedImage img) {
		historyModelView.addAndSaveModel(rootBranchGroup, img);
	}
	
	public void addTmpModel(BufferedImage img) {
		tmpPanel.addTmpModel(rootBranchGroup, img);
	}
}
